package kisti.lod.sindice.query;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import kisti.lod.sindice.SindiceStatusException;
import kisti.lod.sindice.TooManyRequestsException;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import com.sindice.SindiceException;
import com.sindice.query.Query;

public class LiveQuery {

	String liveApiUrl = "http://api.sindice.com/v2/live?explicit=1&implicit=0&ontologies=0&extractors=0";
	Pattern pattern = Pattern.compile("(<[^<>]*)(<)([^<>]*)>");
	//timeouts for connections to sindice (in milliseconds)
	public static final int READ_TIMEOUT = 0;
	public static final int CONNECT_TIMEOUT = 0;
	
	public StringBuffer performQuery(String uri) throws SindiceException, SocketTimeoutException {

		String sindiceUrl = liveApiUrl + "&url=" + Query.encode(uri);

		JSONObject json = performGETQueryAndParseResult(sindiceUrl);
		// error response
		// {"error":"Error from rdfextractor service. Response status: [406][To
		// frequent requests from: 140.203.155.130, 140.203.155.130 Try reduce
		// requests rate to 1req\/1sec]","status":"nok"}
//		if (json == null) {
//			throw new SindiceException("Sindice returned NULL for "
//					+ sindiceUrl);
//		}
		if (json.containsKey("error")) {
			String errorMsg = (String) json.get("error");
			if (errorMsg.contains("[406]")) {
				throw new TooManyRequestsException(
						"Too many requests to Sindice: " + errorMsg);
			} else {
				throw new SindiceException("Sindice Error: " + errorMsg);
			}
		}
		if (!json.get("status").equals("ok")) {
			throw new SindiceStatusException("Status was " + json.get("status"));
		}
		
		JSONObject metadata = (JSONObject) ((JSONObject) json.get("extractorResults")).get("metadata");
		JSONObject explicit = (JSONObject) metadata.get("explicit");
		JSONArray bindings = (JSONArray) explicit.get("bindings");
		
		StringBuffer triples = new StringBuffer();
		for(int i=0;i<bindings.size();i++) {
			JSONObject binding = (JSONObject) bindings.get(i);
			JSONObject sObject = (JSONObject) binding.get("s");
			JSONObject pObject = (JSONObject) binding.get("p");
			JSONObject oObject = (JSONObject) binding.get("o");
			
			String s = valueToNTriple((String) sObject.get("value"), (String) sObject.get("type"));
			String p = valueToNTriple((String) pObject.get("value"), (String) pObject.get("type"));
			String o = valueToNTriple((String) oObject.get("value"), (String) oObject.get("type"));
			
			String triple = s + " " + p + " " + o;
			
			//quick and dirty fix for dbpedia triples using "<" in the resourcename
			if(triple.contains("query=DESCRIBE")) {
				Matcher matcher = pattern.matcher(triple);
				if(matcher.find()) {
					triple = matcher.replaceAll("$1%3C$3%3E>");
				}
			}
			triples.append(triple + " . \n");
		}
		return triples;
	}
	
	protected String valueToNTriple(String value, String type) {
		if(type.equals("uri")) {
			return "<" + value + ">";
		}
//		else return "\"" + value + "\"";
		else if(type.equals("literal")) {
			//escape double quotes (except for first and last) and return
			return "\"" + value.substring(1, (value.length()-1))
							   .replace("\"", "\\\"") + "\"";
		}
		else {
			return value;
		}
		
	}
	
	public static JSONObject performGETQueryAndParseResult(String urlString)
		    throws SindiceException, SocketTimeoutException {
		        final URL url;
		        try {
		            url = new URL(urlString);
		        } catch (MalformedURLException e) {
		            throw new SindiceException("" +
		                    "Unexpected exception with the Sindice API URI. " +
		                    "This should not happen. " +
		                    "Please report a bug on sindice4j project.",
		                    e
		            );
		        }
		        URLConnection con;
		        try {
		            con = url.openConnection();
		            con.setReadTimeout(READ_TIMEOUT);
		        } catch (IOException e) {
		        	 throw new SindiceException("Something wrong happened on server side.", e);
		        }
		        con.setRequestProperty("accept", "application/json");
		        BufferedReader in;
		        try {
		            in = new BufferedReader(new InputStreamReader(con.getInputStream()));
		        } catch (IOException e) {
		        	throw new SindiceException("A problem occurred while accessing the output from Sindice.", e);
		        }
		        try {
		            return (JSONObject) JSONValue.parse(in);
		        } catch (Exception e) {
		            throw new SindiceException("An error occurred while parsing the JSON response.", e);
		        }
		    }
}